import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;

import datastructuur.*;

/** 
 * De parser herkent zinnen van een simpele grammatica.
 * Deze grammatica is G(&lt;expression&gt;)<br><br>
 * &nbsp;&lt;expression&gt; ::= &lt;term&gt; &lt;expression-tail&gt;<br>
 * &nbsp;&lt;expression-tail&gt; ::= e | &lt;plus-expression&gt; | &lt;minus-term&gt;<br>
 * &nbsp;&lt;plus-expression&gt; ::= PLUS &lt;expression&gt;<br>
 * &nbsp;&lt;minus-term&gt; ::= MINUS &lt;term&gt; &lt;expression-tail&gt;<br>
 * &nbsp;&lt;term&gt; ::= &lt;factor&gt; &lt;term-tail&gt;<br>
 * &nbsp;&lt;term-tail&gt; ::= e | &lt;times-term&gt;<br>
 * &nbsp;&lt;times-term&gt; ::= TIMES &lt;term&gt;<br>
 * &nbsp;&lt;factor&gt; ::= PARENT_LEFT &lt;expression&gt; PARENT_RIGHT<br>
 * &nbsp;&lt;factor&gt; ::= ID | INT | LONG | FLOAT | DOUBLE<br>
 * [Namen in hoofdletters zijn tokens die door de scanner herkend worden.]<br>
 * Het resultaat van parsen is een string representatie van de oorspronkelijke
 * expressie met daaraan haakjes toegevoegd die de volgorde van evaluatie
 * duidelijk maakt. Bijvoorbeeld, de invoer expressie <code>1+2*3</code>
 * resulteert in <code>(1+(2*3))</code>, omdat de vermenigvuldig operator
 * sterker bindt (en dus eerder wordt ge&euml;valueerd) dan de optel operator.
 */
class Parser implements TokenKinds {
	
	private Grammatica gram;

  /**
   * De scanner die de tokens levert voor deze parser.
   */
  private Scanner scanner;

  /**
   * 1 lookahead voor een LL(1) parser.
   */
  private Token lookahead;

  /**
   * Een fout is opgetreden bij het parsen van de nonterminal.
   * @param nonterminal naam van nonterminal
   * @param message foutmelding
   */
  private void parseError(String nonterminal,String message) {
    System.err.println();
    System.err.println("parse error in <"+nonterminal+"> : "+message);
    System.err.print("lookahead '"+lookahead.getText()+"' ");
    System.err.print("(kind="+lookahead.getKind()+") ");
    System.err.print("at "+lookahead.getLine()+"/"+lookahead.getColumn());
    System.err.println();
    throw new RuntimeException();
  }

  /**
   * Accepteer huidige token van scanner.
   */
  private String acceptIt() {
	  String s = "Error! Werdt niet geaccept in acceptIt()";
    try {
    	if(lookahead!=null) s = lookahead.getText();
      lookahead=scanner.nextToken();
    } catch (IOException exception) {
    	System.out.println("Error! in acceptIt");
      exception.printStackTrace();
      throw new RuntimeException();
      
    }
    return s;
  }
 
  /**
   * Accepteer huidige token van scanner, mits deze van een bepaald soort is.
   * @param nonterminal naam van nonterminal
   * @param kind identifatie van token soort
   */
  private String accept(String nonterminal,int kind) {
    if ( lookahead.getKind()!=kind ) {
      parseError(nonterminal,"kind="+kind+" expected");
    } else {
      return acceptIt();
    }
    return "Error! Werdt niet geaccept in accept()";
  }

  /**
   * Construeer een nieuwe parser op de input stream.
   * @param in input stream van characters
   */
  public Parser(InputStream in) {
    scanner=new Scanner(new BufferedReader(new InputStreamReader(in)));
    //Initialiseer lookahead met eerste token van scanner
    acceptIt();
  }

  /**
   * Parse het root symbool van de grammatica.
   * @return het resultaat van parsing (in dit geval een string)
   */
  public Object parseRoot() {
    return parseBNF();
  }

  /**
   * Een interne stack om string representatie van expressie te kunnen bouwen.
   */
  private LinkedList expressionStack=new LinkedList();

  /**
   * Push (tekst van) current token op de interne stack.
   */
  private void pushIt() {
    push(lookahead.getText());
  }

  /**
   * Push een object op de interne stack.
   * @param object object dat bovenop de stapel wordt geplaatst
   */
  private void push(Object object) {
    expressionStack.addFirst(object);
  }

  /**
   * Haal bovenste object van de interne stack.
   * @return bovenste verwijderde element
   */
  private Object pop() {
    return expressionStack.removeFirst();
  }
  
  private Grammatica parseBNF(){
	  Grammatica g = new Grammatica();
	  g.addRegel(parseRule());
	  for(Regel r : parseRestRules())
	  {
		  g.addRegel(r);
	  }
	  accept("parseBNF-eof",EOF);
	  System.out.println(g);
	  return g;
  }
  
  private ArrayList<Regel> parseRestRules(){
	  ArrayList<Regel> r = new ArrayList<Regel>();
	  if(lookahead.getKind() == NONTERMINAL ){
		  r.add(parseRule());
		  for(Regel q : parseRestRules())
		  {
			  r.add(q);
		  }
	  } 
	  return r;
  }
  
  private Regel parseRule(){
	  Regel r = new Regel();
	  r.setGedefinieerde(new NonTerminal(accept("parseRule-NONTERMINAL", NONTERMINAL)));
	  accept("paseRule-ISDEFINEDAS", ISDEFINEDAS);
	  r.setAlternatieven(parseAlternatives());
	  accept("parseRule-EOL", EOL);
	  return r;
  }
  
  private ArrayList<Alternatief> parseAlternatives(){
	  ArrayList<Alternatief> a = new ArrayList<Alternatief>();
	  a.add(parseOneAlternative());
	  for(Alternatief q : parseOtherAlternatives())
	  {
		  a.add(q);
	  }
	  return a;
  }
  
  private ArrayList<Alternatief> parseOtherAlternatives(){
	  ArrayList<Alternatief> a = new ArrayList<Alternatief>();
	  if(lookahead.getKind() == OR){
		  accept("parseOtherAlternatives-OR", OR);
		  for(Alternatief q : parseAlternatives())
		  {
			  a.add(q);
		  }
	  }
	  return a;
  }
  
  private Alternatief parseOneAlternative(){
	  Alternatief a = new Alternatief();
	  switch(lookahead.getKind()){
	  case EMPTY:
		  accept("parseOneAlternative-EMPTY", EMPTY);
		  return null;
	case TERMINAL:
	  case NONTERMINAL:
		  a.addTerm(parseTerm());
		  for( Term t : parseRestAlternative() )
		  {
			  a.addTerm(t);
		  }
		  break;
	  default:
		  parseError("oneAlternative","Not expected = " + lookahead.getKind());
	  }
	  return a;
  }
  private ArrayList<Term> parseRestAlternative(){
	  ArrayList<Term> t = new ArrayList<Term>();
	  if(lookahead.getKind() == TERMINAL || lookahead.getKind() == NONTERMINAL){
		  t.add(parseTerm());
		  for( Term q : parseRestAlternative() )
		  {
			  t.add(q);
		  }
	  }
	  return t;
  }
  private Term parseTerm(){
	  switch(lookahead.getKind()){
	  case TERMINAL:
		  Term t = new Terminal(accept("parseTerm-TERMINAL", TERMINAL));
		  return t;
	  case NONTERMINAL:
		  return new NonTerminal(accept("parseTerm-NONTERMINAL", NONTERMINAL));
	  default:
		  parseError("parseTerm","Not expected = " + lookahead.getKind());
		  return null;
	  }
  }

}
